package git.bankview.sl.report;

import java.util.*;
import java.sql.*;
import java.io.*;

import laya.semi.process.*;

import git.bankview.sl.db.*;
import git.bankview.sl.util.ArrayUtil;
import git.bankview.sl.auth.AuthHelper;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.export.*;
import net.sf.jasperreports.engine.util.*;
import net.sf.jasperreports.engine.fill.*;

import org.apache.log4j.Logger;

public class ReportProcessThread extends Thread
{
	private static Logger log = Logger.getLogger(ReportProcessThread.class);
	
	private ReportScheduler m_parent = null;
	private boolean m_bTerminateFlag = false;
	private boolean m_bIsWaitForNewSchd = false;
	private int m_nPollInterval = -1;
	private int m_nDelayAfterReturnSchedule = 2000;
	private String m_oWrongExprText = null;
	private int m_nLastState = -1;
	
	public ReportProcessThread(ReportScheduler parent, int threadIndex, int pollInterval)
	{
		super(String.valueOf(threadIndex));
		m_parent = parent;
		m_nPollInterval = pollInterval;
	}
	
	public ReportProcessThread(ReportScheduler parent, int threadIndex, int pollInterval, int delayAfterReturnSchedule)
	{
		super(String.valueOf(threadIndex));
		m_parent = parent;
		m_nPollInterval = pollInterval;
		m_nDelayAfterReturnSchedule = delayAfterReturnSchedule;
	}
	
	public void run()
	{
		while(!m_bTerminateFlag)
		{
			int nSchdId = -1;
			while(-1 == (nSchdId = m_parent.releaseSchedule()))
			{
				m_bIsWaitForNewSchd = true;
				try
				{
					sleep(m_nPollInterval);
					m_bIsWaitForNewSchd = false;
				}
				catch(InterruptedException e)
				{
					m_bIsWaitForNewSchd = false;
					log.debug(getName() + ".INTERRUPTED." + e.getMessage());
				}
				if (m_bTerminateFlag)
				{
					log.debug(getName() + ".TERMINATED");
					return;
				}
			}
			
			m_oWrongExprText = null;
			//String oDestTableName = null; // ETL dest tables, may be not need
			
			Connection oConfConn = null;
			
			// check nSchdId dependence relationship
			try
			{
				m_nLastState = -1;
				ReportSchd oSchd = SchdUtil.getReportSchdById(nSchdId);
				
				switch(oSchd.getSchdstatus())
				{
					case ReportCommon.SCHD_STATUS_READYTODELETE:
					case ReportCommon.SCHD_STATUS_DELETED:
						m_nLastState = oSchd.getSchdstatus();
						writeJournalFinished(nSchdId, null);
						log.debug(getName() + "." + nSchdId + ".SCHD_CANCELED");
						continue;
					case ReportCommon.SCHD_STATUS_READYTORUN:
						break;
					default:
						m_nLastState = -9;
						writeJournalFinished(nSchdId, null);
						log.debug(getName() + "." + nSchdId + ".SCHD_WRONGSTATE_" + oSchd.getSchdstatus());
						continue;
				}
				
				m_nLastState = -2;
				// analyze dependences by schd
				if (SchdUtil.hasDependSchd(oSchd))
				{
					m_parent.addSchedule(nSchdId); // return the schedule back
					log.debug(getName() + "." + nSchdId + ".SCHD_RETURNBACK0");
					sleep(m_nDelayAfterReturnSchedule);
					continue;
				}
				
				// analyze dependences by load policy(table) if need
				String[] vLoadPolicy = 
					SchdUtil.getReportPropertiesByName(oSchd.getTransid(), "REPORT_LOAD_POLICY");
				String[] vLoadTable = 
					SchdUtil.getReportPropertiesByName(oSchd.getTransid(), "REPORT_LOAD_TABLE");
				String oLoadPolicy = null;
				String oLoadTable = null;
				if (0 < vLoadPolicy.length)
				{
					oLoadPolicy = vLoadPolicy[0];
					oLoadTable = vLoadTable[0];
					/*
					if (!acquireTableForUpdate(oDestTableName))
					{
						m_parent.addSchedule(nSchdId); // return the schedule back
						log.debug(getName() + "." + nSchdId + ".SCHD_RETURNBACK1." + oDestTableName);
						oDestTableName = null;
						sleep(m_nDelayAfterReturnSchedule);
						continue;
					}
					*/
				}

				m_nLastState = -99;
				log.debug(getName() + "." + nSchdId + ".SCHD_START");
				writeJournalRunning(nSchdId);
				
				//processReport(oSchd, oLoadPolicy, oLoadTable);
				try
				{
					SchdUtil.handleReport(oSchd, oLoadPolicy, oLoadTable, null);
					m_nLastState = ReportCommon.SCHD_STATUS_OK;
				}
				catch(ReportSchdException e)
				{
					//log.debug(getName() + "." + nSchdId + ":" + e.getSchdState(), e);
					
					m_nLastState = e.getSchdState();
					m_oWrongExprText = e.getMessage();
					throw e;
				}
				
				writeJournalFinished(nSchdId, null);
				log.debug(getName() + "." + nSchdId + ".SCHD_FINISHED");
			}
			catch(Throwable t)
			{
				log.warn(getName() + "." + nSchdId + ".SCHD_ERROR.(" + m_nLastState + ")");
				
				try
				{
					writeJournalFinished(nSchdId, t);
				}
				catch(Exception e)
				{
					log.warn("write journal error: ", e);
				}
			}
			finally
			{
				/*if (null != oDestTableName)
					releaseTable(oDestTableName);
					*/
			}
		}
	}
	
	private void writeJournalRunning(int nSchdId)
		throws Exception
	{
		Connection oConn = DBConnectionFactory.getCoreConnection();
		try
		{
			PreparedStatement ps = oConn.prepareStatement("update dqschdentry set schdstatus = ?, exectime = ?, execmsg = ? where schdid = ?");
			ps.setInt(1, ReportCommon.SCHD_STATUS_RUNNING);
			ps.setTimestamp(2, new Timestamp(new java.util.Date().getTime()));
			ps.setString(3, "reportSchd.processUnfinished");
			ps.setInt(4, nSchdId);
			ps.executeUpdate();
		}
		finally
		{
			oConn.close();
		}		
	}
	/*
	private void processReport(ReportSchd oSchd, String oLoadPolicy, String oLoadTable)
		throws Exception
	{
		setContextClassLoader(SchdUtil.getAccessoryClassLoader(oSchd.getTransid()));
		
		m_nLastState = -3;
		File reportFile = SchdUtil.getMainReportFile(oSchd);
		log.debug(getName() + ".LOAD_REPORT." + oSchd.getSchdid());
		m_nLastState = -4;
		JasperReport jasperReport = (JasperReport)JRLoader.loadObject(reportFile);
		JasperPrint jasperPrint = null;
		m_nLastState = -51;
		String oTranslabel = oSchd.getTransid(); // default set translabel to transid
		Connection oMainConn = DBConnectionFactory.getCoreConnection();
		try
		{
			log.debug(getName() + ".ASSIGN_PARAMETERS." + oSchd.getSchdid());
			SchdUtil.assignUnfilledParameters(oSchd, jasperReport, reportFile.getParent() + "/", oMainConn);
			log.debug(getName() + ".GET_TRANSLABEL." + oSchd.getSchdid());
			oTranslabel = AuthHelper.getTransLabelByTransId(oSchd.getTransid(), oMainConn);
		}
		finally
		{
			oMainConn.close();
		}
		
		m_nLastState = -52;
		Connection oReportConn = getReportConnection(jasperReport);
		try
		{
			// try to execute sql before fill the report
			String execSqlBeforeFillReport = 
				jasperReport.getProperty(ReportCommon.GRPN_EVENT_BEFOREFILLREPORT_SQL);

			if (null != execSqlBeforeFillReport && 0 < execSqlBeforeFillReport.trim().length())
			{
				log.debug(getName() + ".EXEC_SQL_BEFORE_FILL_REPORT." + oSchd.getSchdid());
				m_nLastState = -61;
				new JRRuntimeUtil(oSchd.getReportParameterMap(), oReportConn).execute(execSqlBeforeFillReport);
			}
			
			// try to export table directly
			String directExportTableName = 
				jasperReport.getProperty(ReportCommon.GRPN_EXPORT_TABLENAME);
			
			if (null != directExportTableName && 0 < directExportTableName.trim().length())
			{
				log.debug(getName() + ".DIRECT_EXPORT_TABLE." + oSchd.getSchdid());
				m_nLastState = -65;
				
				String exportFieldList = 
					jasperReport.getProperty(ReportCommon.GRPN_EXPORT_FIELDS);
				
				String sourceQuerySql = jasperReport.getQuery().getText();
				StringBuffer execSql = new StringBuffer("insert into ")
					.append(directExportTableName)
					.append(" (").append(exportFieldList).append(")");
				
				new JRRuntimeUtil(oSchd.getReportParameterMap(), oReportConn).execute(execSql.toString());
				m_nLastState = ReportCommon.SCHD_STATUS_OK;
				return;
			}
			
			log.debug(getName() + ".FILL_REPORT." + oSchd.getSchdid());
			m_nLastState = -68;
			jasperPrint = 
				JasperFillManager.fillReport(jasperReport, oSchd.getReportParameterMap(), oReportConn);
		}
		catch(JRExpressionEvalException e)
		{
			if (null != e.getExpression())
				m_oWrongExprText = e.getExpression().getText();
				
			m_nLastState = -7;
			throw e;
		}
		catch(JRScriptletException e)
		{
			m_nLastState = -8;
			throw e;
		}
		finally
		{
			oReportConn.close();
		}

		JRExporter exporter = null;
		if (ReportCommon.SCHD_EXPORT_PDF.equals(oSchd.getSchdexport()))
		{
			m_nLastState = -11;
			exporter = new JRPdfExporter();
		}
		else if (ReportCommon.SCHD_EXPORT_XML.equals(oSchd.getSchdexport()))
		{
			m_nLastState = -12;
			exporter = new JRXmlExporter();
		}
		else if (ReportCommon.SCHD_EXPORT_EXCEL.equals(oSchd.getSchdexport()))
		{
			m_nLastState = -13;
			exporter = new JRXlsExporter();
			//exporter.setParameter(JRXlsExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
			exporter.setParameter(JRXlsExporterParameter.SHEET_NAMES, new String[]{oTranslabel});
		}
		else if (ReportCommon.SCHD_EXPORT_HTML.equals(oSchd.getSchdexport()))
		{
			m_nLastState = -14;
			exporter = new JRHtmlExporter();
			exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN, Boolean.FALSE);
		}
		else if (ReportCommon.SCHD_EXPORT_CSV.equals(oSchd.getSchdexport()))
		{
			m_nLastState = -15;
			exporter = new JRCsvExporter();
 			exporter.setParameter(JRExporterParameter.CHARACTER_ENCODING, ReportCommon.DB_CLIENTCHARSETNAME);
		}
		else
		{
			m_nLastState = -10;
		}
		
		exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
		File exportFile = SchdUtil.getReportExportFile(oSchd);
		exporter.setParameter(JRExporterParameter.OUTPUT_FILE, exportFile);
		
		log.debug(getName() + ".EXPORT_REPORT." + oSchd.getSchdid());
		exporter.exportReport();
		
		String oSchdUser = (String)oSchd.getReportParameterMap().get(ReportCommon.GRP_SCHD_USER);
		
		// if meet CSV, try to load CSV data to DB
		if (-15 == m_nLastState && null != oLoadPolicy && ReportCommon.GRP_SCHD_USER_system.equals(oSchdUser))
		{
			synchronized(acquireTableForUpdate(oLoadTable))
			{
				m_nLastState = -20;
				Connection oConn = DBConnectionFactory.getGenericConnection();
				try
				{
					log.debug(getName() + ".LOAD_DATA_TO_DB." + oSchd.getSchdid());
					
					// tab, file, update test first, need append, conn
					//boolean bNeedAppend = !"UPDATEONLY".equals(oLoadPolicy);
					//boolean bUpdateFirst = "UNION".equals(oLoadPolicy) || "UPDATEONLY".equals(oLoadPolicy);
					//new TableImporter().dataImport(oLoadTable, exportFile, bNeedAppend, bUpdateFirst, bDeleteFirst, oConn);
					//loadToDB(oLoadTable, exportFile, bNeedAppend, bUpdateFirst, oConn);
					
					new TableImporter(oLoadPolicy).dataImport(oLoadTable, exportFile, oConn);
				}
				catch(TableImporterException e)
				{
					m_oWrongExprText = e.getMessage();
					throw e;
				}
				finally
				{
					oConn.close();
				}
			}
		}
		
		m_nLastState = ReportCommon.SCHD_STATUS_OK;
	}
	*/
	
	//private static Map m_oLoadTables = new Hashtable();
	
/*	
	public static boolean acquireTableForUpdate(String tabName)
	{
		synchronized(m_oLoadTables)
		{
			Boolean b = (Boolean)m_oLoadTables.get(tabName);
			if (null == b || Boolean.FALSE == b)
			{
				b = Boolean.TRUE;
				m_oLoadTables.put(tabName, b);
				return true;
			}
			
			return false;
		}
	}
*/
/*
	public synchronized static String acquireTableForUpdate(String tabName)
	{
		String mutex = (String)m_oLoadTables.get(tabName);
		if (null == mutex)
		{
			mutex = tabName;
			m_oLoadTables.put(tabName, mutex);
		}
		
		return mutex;
	}
	
	private Connection getReportConnection(JasperReport jasperReport)
		throws Exception
	{
		// look for datasource name in properties of report object and use it if exists
		String dsName = jasperReport.getProperty(ReportCommon.GRPN_JDBC_DATASOURCENAME);
		if (null != dsName)
		{
			return DBConnectionFactory.getConnection(dsName);
		}
		
		// look for jdbc driver name in properties of report object and use it if exists
		String driver = jasperReport.getProperty(ReportCommon.GRPN_JDBC_DRIVER);
		if (null != driver)
		{
			return DBConnectionFactory.getConnection(driver
				, jasperReport.getProperty(ReportCommon.GRPN_JDBC_URL)
				, jasperReport.getProperty(ReportCommon.GRPN_JDBC_USERNAME)
				, jasperReport.getProperty(ReportCommon.GRPN_JDBC_PASSWORD));
		}
		
		// by default, create a generic connection
		return DBConnectionFactory.getGenericConnection();
	}
	*/
	
	/*
	public static void releaseTable(String tabName)
	{
		synchronized(m_oLoadTables)
		{
			Boolean b = (Boolean)m_oLoadTables.get(tabName);
			if (null != b && Boolean.FALSE == b)
			{
				throw new AssertionError();
			}
			
			m_oLoadTables.put(tabName, Boolean.FALSE);
		}
	}
	private void loadToDB(String tabName, File src, boolean bNeedAppend, boolean bUpdateFirst, Connection conn)
		throws Exception
	{
		boolean bNeedConvertCharset = !ReportCommon.DB_CHARSETNAME.equals(ReportCommon.DB_CLIENTCHARSETNAME);
		
		BufferedReader reader = 
			new BufferedReader(new InputStreamReader(new FileInputStream(src), ReportCommon.DB_CLIENTCHARSETNAME));
		
		String line = reader.readLine();
		if (null == line)
			return; // empty file
		
		String[] vFieldExpr = line.split(",");
		if (0 == vFieldExpr.length)
			return; // zero field file
		
		m_oWrongExprText = line;
		
		PreparedStatement psAppend = null;
		if (bNeedAppend)
		{
			StringBuffer oSql = new StringBuffer();
			buildAppendSql(oSql, vFieldExpr, tabName);
			psAppend = conn.prepareStatement(oSql.toString());
		}
		PreparedStatement psUpdate = null;
		int[] vUpdateFieldQ = null;
		if (bUpdateFirst)
		{
			StringBuffer oSql = new StringBuffer();
			vUpdateFieldQ = buildUpdateSql(oSql, vFieldExpr, tabName);
			psUpdate = conn.prepareStatement(oSql.toString());
		}
		
		while(null != (line = reader.readLine()))
		{
			if (bNeedConvertCharset)
				line = new String(line.getBytes(ReportCommon.DB_CLIENTCHARSETNAME), ReportCommon.DB_CHARSETNAME);
				
			m_oWrongExprText = line;
			
			String[] v = line.split(",");
			//log.debug(ArrayUtil.toString(v));
			boolean bUpdate = false;
			
			if (bUpdateFirst)
			{
				//log.debug("UPDATE order: " + ArrayUtil.toString(vUpdateFieldQ));
				for (int i = 0; i < vFieldExpr.length; ++i)
				{
					psUpdate.setString(i + 1, v[vUpdateFieldQ[i]]);
				}
				
				bUpdate = (0 < psUpdate.executeUpdate());
			}
			
			if (bNeedAppend && !bUpdate)
			{
				for (int i = 0; i < vFieldExpr.length; ++i)
				{
					psAppend.setString(i + 1, v[i]);
				}
				
				psAppend.executeUpdate();
			}
		}
		
		m_oWrongExprText = null;
	}
	
	private void buildAppendSql(StringBuffer oSql, String[] vFieldExpr, String tabName)
	{
		oSql.append("insert into ").append(tabName).append(" (");
		for (int i = 0; i < vFieldExpr.length; ++i)
		{
			String fieldName = 
				('*' == vFieldExpr[i].charAt(0) ? vFieldExpr[i].substring(1) : vFieldExpr[i]);
			
			if (0 < i)
				oSql.append(",");
			
			oSql.append(fieldName);	
		}
		oSql.append(") values (");
		for (int i = 0; i < vFieldExpr.length; ++i)
		{
			if (0 < i)
				oSql.append(",");
			
			oSql.append("?");	
		}
		oSql.append(")");
		
		log.debug(oSql);
	}
	
	private int[] buildUpdateSql(StringBuffer oSql, String[] vFieldExpr, String tabName)
	{
		int[] vUpdateFieldQ = new int[vFieldExpr.length];
		int idxHead = -1;
		
		oSql.append("update ").append(tabName).append(" set ");
		for (int i = 0; i < vFieldExpr.length; ++i)
		{
			if ('*' == vFieldExpr[i].charAt(0))
			{
				// pk
				vUpdateFieldQ[vUpdateFieldQ.length + idxHead - i] = i;
			}
			else
			{
				// non-pk
				vUpdateFieldQ[++idxHead] = i;
				if (0 < idxHead)
					oSql.append(", ");
				
				oSql.append(vFieldExpr[i]).append(" = ?");
			}
		}
		oSql.append(" where ");
		for (int i = idxHead + 1; i < vFieldExpr.length; ++i)
		{
			if (idxHead + 1 < i)
				oSql.append(" and ");
			
			oSql.append(vFieldExpr[vUpdateFieldQ[i]].substring(1)).append(" = ?");
		}
		
		log.debug(oSql);
		return vUpdateFieldQ;
	}
	*/
	
	private void writeJournalFinished(int nSchdId, Throwable t)
		throws Exception
	{
		byte[] stackTraceBytes = null;
		if (null != t)
		{
			ByteArrayOutputStream stackTraceStream = new ByteArrayOutputStream();
			PrintStream stackTracePrintStream = new PrintStream(stackTraceStream);
			stackTracePrintStream.print("Wrong expression: ");
			stackTracePrintStream.println(m_oWrongExprText);
			t.printStackTrace(stackTracePrintStream);
			stackTraceBytes = stackTraceStream.toByteArray();
		}
		
		Connection oConn = DBConnectionFactory.getCoreConnection();
		try
		{
			PreparedStatement ps = oConn.prepareStatement("update dqschdentry set schdstatus = ?, termtime = ?, execmsg = ?, execdetail = ? where schdid = ?");
			ps.setInt(1, m_nLastState);
			ps.setTimestamp(2, new Timestamp(new java.util.Date().getTime()));
			
			String oLastStateMsg = null;
			switch(m_nLastState)
			{
				case -1: oLastStateMsg = "reportSchd.err.cannotGetSchd"; break;
				case -2: oLastStateMsg = "reportSchd.err.cannotGetStatus"; break;
				case -3: oLastStateMsg = "reportSchd.err.cannotGetReportFile"; break;
				case -4: oLastStateMsg = "reportSchd.err.cannotGetReportObject"; break;
				case -51: oLastStateMsg = "reportSchd.err.cannotGetCoreConnection"; break;
				case -52: oLastStateMsg = "reportSchd.err.cannotGetReportConnection"; break;
				case -61: oLastStateMsg = "reportSchd.err.execSqlBeforeFillReport"; break;
				case -65: oLastStateMsg = "reportSchd.err.directExport"; break;
				case -68: oLastStateMsg = "reportSchd.err.cannotFillReport"; break;
				case -7: oLastStateMsg = "reportSchd.err.expressionEvalError"; break;
				case -8: oLastStateMsg = "reportSchd.err.scriptletError"; break;
				case -9: oLastStateMsg = "reportSchd.err.wrongState"; break;
				case -10: oLastStateMsg = "reportSchd.err.unknownExportType"; break;
				case -11: oLastStateMsg = "reportSchd.err.cannotExportToPdf"; break;
				case -12: oLastStateMsg = "reportSchd.err.cannotExportToXml"; break;
				case -13: oLastStateMsg = "reportSchd.err.cannotExportToXls"; break;
				case -14: oLastStateMsg = "reportSchd.err.cannotExportToHtml"; break;
				case -15: oLastStateMsg = "reportSchd.err.cannotExportToCsv"; break;
				case -20: oLastStateMsg = "reportSchd.err.cannotLoadToDB"; break;
				case ReportCommon.SCHD_STATUS_OK: oLastStateMsg = "reportSchd.processFinished"; break;
				case ReportCommon.SCHD_STATUS_READYTODELETE: 
				case ReportCommon.SCHD_STATUS_DELETED: oLastStateMsg = "reportSchd.err.hasCanceled"; break;
				default: oLastStateMsg = "reportSchd.err.unknowError"; break;
			}
			ps.setString(3, oLastStateMsg);
			ps.setBytes(4, stackTraceBytes);
			ps.setInt(5, nSchdId);
			ps.executeUpdate();
		}
		finally
		{
			oConn.close();
		}
	}
	
	public boolean isWaitForNewSchd()
	{
		return m_bIsWaitForNewSchd;
	}
	
	public void setTerminateFlag()
	{
		m_bTerminateFlag = true;
		if (isWaitForNewSchd())
			interrupt();
	}
}
